﻿// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#nullable disable

using Microsoft.DotNet.Cli.Utils;
using Microsoft.NET.Sdk.WorkloadManifestReader;
using NuGet.Versioning;

namespace Microsoft.DotNet.MsiInstallerTests.Framework
{
    [Collection("VM Tests")]
    public class VMTestBase : SdkTest, IDisposable
    {
        internal VirtualMachine VM { get; }

        public VMTestBase(ITestOutputHelper log) : base(log)
        {
            VM = new VirtualMachine(Log);
            _sdkInstallerVersion = new Lazy<string>(() =>
            {
                if (!string.IsNullOrEmpty(VM.VMTestSettings.SdkInstallerVersion))
                {
                    return VM.VMTestSettings.SdkInstallerVersion;
                }
                else
                {
                    var sdkTestingDir = VM.GetRemoteDirectory(@"c:\SdkTesting", mustExist: true);

                    string installerPrefix = "dotnet-sdk-";
                    string installerSuffix = "-win-x64.exe";

                    List<string> sdkInstallerVersions = new List<string>();
                    foreach (var file in sdkTestingDir.Files.Select(f => Path.GetFileName(f)))
                    {
                        if (file.StartsWith(installerPrefix) && file.EndsWith(installerSuffix))
                        {
                            sdkInstallerVersions.Add(file.Substring(installerPrefix.Length, file.Length - installerPrefix.Length - installerSuffix.Length));
                        }
                    }

                    if (sdkInstallerVersions.Count == 0)
                    {
                        throw new Exception("No SDK installer found on VM");
                    }

                    return sdkInstallerVersions.MaxBy(v => new NuGetVersion(v));
                }
            });
        }

        public virtual void Dispose()
        {
            VM.Dispose();
        }

        protected virtual bool NeedsIncludePreviews => false;

        Lazy<string> _sdkInstallerVersion;
        private bool _sdkInstalled = false;

        protected string SdkInstallerVersion => _sdkInstallerVersion.Value;

        protected string SdkInstallerFileName => $"dotnet-sdk-{SdkInstallerVersion}-win-x64.exe";

        protected void InstallSdk(bool deployStage2 = true)
        {
            if (_sdkInstalled)
            {
                return;
            }
            IEnumerable<VMAction> AddNuGetSourceActions = Enumerable.Empty<VMAction>();
            if (VM.VMTestSettings.NuGetSourcesToAdd != null)
            {
                AddNuGetSourceActions = VM.VMTestSettings.NuGetSourcesToAdd.Select(source =>

                    VM.CreateRunCommand("dotnet", "nuget", "add", "source", source)
                ).ToList();
            }

            VM.CreateActionGroup($"Install and setup SDK {SdkInstallerVersion}",
                    [
                        VM.CreateRunCommand("setx", "DOTNET_NOLOGO", "true"),
                        VM.CreateRunCommand($@"c:\SdkTesting\{SdkInstallerFileName}", "/quiet"),
                        ..AddNuGetSourceActions
                    ])
                .Execute().Should().Pass();
            if (deployStage2)
            {
                DeployStage2Sdk();
            }

            _sdkInstalled = true;
        }

        protected void UninstallSdk()
        {
            VM.CreateRunCommand($@"c:\SdkTesting\{SdkInstallerFileName}", "/quiet", "/uninstall")
                .WithDescription($"Uninstall SDK {SdkInstallerVersion}")
                .Execute()
                .Should()
                .Pass();
        }

        protected void DeployStage2Sdk()
        {
            if (!VM.VMTestSettings.ShouldTestStage2)
            {
                return;
            }


            //  Install any runtimes that are in the c:\SdkTesting directory, to support using older baseline SDK versions with a newer stage 2
            var sdkTestingDir = VM.GetRemoteDirectory(@"c:\SdkTesting");
            List<string> runtimeInstallers = new List<string>();
            string installerPrefix = "dotnet-runtime-";
            string installerSuffix = "-win-x64.exe";
            foreach (var file in sdkTestingDir.Files.Select(Path.GetFileName))
            {
                if (file.StartsWith(installerPrefix) && file.EndsWith(installerSuffix))
                {
                    runtimeInstallers.Add(file);
                }
            }

            if (runtimeInstallers.Any())
            {
                VM.CreateActionGroup($"Install .NET runtime(s)",
                        runtimeInstallers.Select(i => VM.CreateRunCommand($@"c:\SdkTesting\{i}", "/quiet")).ToArray())
                    .Execute().Should().Pass();
            }

            var result = VM.CreateRunCommand("dotnet", "--version")
                .WithIsReadOnly(true)
                .Execute();

            result.Should().PassWithoutWarning();

            string existingVersionToOverwrite = result.StdOut;

            var installedSdkFolder = $@"c:\Program Files\dotnet\sdk\{existingVersionToOverwrite}";

            Log.WriteLine($"Deploying SDK from {TestContext.Current.ToolsetUnderTest.SdkFolderUnderTest} to {installedSdkFolder} on VM.");

            //  TODO: It would be nice if the description included the date/time of the SDK build, to distinguish different snapshots
            VM.CreateActionGroup("Deploy Stage 2 SDK",
                    VM.CopyFolder(TestContext.Current.ToolsetUnderTest.SdkFolderUnderTest, installedSdkFolder),
                    ChangeVersionFileContents(existingVersionToOverwrite))
                .Execute()
                .Should()
                .Pass();
        }

        protected void ChangeSdkVersion(string oldVersion, string newVersion)
        {
            var oldSdkFolder = $@"c:\Program Files\dotnet\sdk\{oldVersion}";
            var newSdkFolder = $@"c:\Program Files\dotnet\sdk\{newVersion}";

            new VMMoveFolderAction(VM)
            {
               SourcePath = oldSdkFolder,
               TargetPath = newSdkFolder
            }
                .WithDescription($"Change SDK version to {newVersion}")
                .Execute().Should().Pass();

            ChangeVersionFileContents(newVersion)
                .WithDescription("Update .version file")
                .Execute()
                .Should()
                .Pass();

        }

        private VMWriteFileAction ChangeVersionFileContents(string sdkVersion)
        {
            var installedSdkFolder = $@"c:\Program Files\dotnet\sdk\{sdkVersion}";
            var vmVersionFilePath = Path.Combine(installedSdkFolder, ".version");

            var newVersionFileContents = File.ReadAllLines(Path.Combine(TestContext.Current.ToolsetUnderTest.SdkFolderUnderTest, ".version"));
            newVersionFileContents[1] = sdkVersion;

            return VM.WriteFile(vmVersionFilePath, string.Join(Environment.NewLine, newVersionFileContents));

        }

        protected string GetInstalledSdkVersion()
        {
            var command = VM.CreateRunCommand("dotnet", "--version");
            command.IsReadOnly = true;
            var result = command.Execute();
            result.Should().PassWithoutWarning();
            return result.StdOut;
        }

        protected CommandResult InstallWorkload(string workloadName, bool skipManifestUpdate)
        {
            string [] args = { "dotnet", "workload", "install", workloadName};
            if (NeedsIncludePreviews)
            {
                args = [.. args, "--include-previews"];
            }
            if (skipManifestUpdate)
            {
                args = [.. args, "--skip-manifest-update"];
            }

            var result = VM.CreateRunCommand(args)
                    .WithDescription($"Install {workloadName} workload")
                    .Execute();

            result.Should().PassWithoutWarning();

            return result;
        }

        protected WorkloadSet GetRollback(string directory = null)
        {
            var result = VM.CreateRunCommand("dotnet", "workload", "update", "--print-rollback")
                .WithWorkingDirectory(directory)
                .WithIsReadOnly(true)
                .Execute();

            result.Should().PassWithoutWarning();

            return ParseRollbackOutput(result.StdOut);
        }

        protected WorkloadSet ParseRollbackOutput(string output)
        {
            var filteredOutput = string.Join(Environment.NewLine,
                output.Split(Environment.NewLine)
                .Except(["==workloadRollbackDefinitionJsonOutputStart==", "==workloadRollbackDefinitionJsonOutputEnd=="]));

            return WorkloadSet.FromJson(filteredOutput, defaultFeatureBand: new SdkFeatureBand(SdkInstallerVersion));
        }

        protected string GetWorkloadVersion()
        {
            var result = VM.CreateRunCommand("dotnet", "workload", "--version")
                .WithIsReadOnly(true)
                .Execute();

            result.Should().PassWithoutWarning();

            return result.StdOut;
        }

        protected void AddNuGetSource(string source)
        {
            VM.CreateRunCommand("dotnet", "nuget", "add", "source", source)
                .WithDescription($"Add {source} to NuGet.config")
                .Execute()
                .Should()
                .PassWithoutWarning();
        }
    }
}
